using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace Gko.Utils
{
	/// <summary>
	/// Utility class to wrap an unmanaged DLL and be responsible for freeing it.
	/// </summary>
	/// <remarks>This is a managed wrapper over the native LoadLibrary, GetProcAddress, and
	/// FreeLibrary calls.
	/// </remarks>
	public sealed class UnmanagedLibrary : DisposableBase
	{
		/// <summary>
		/// Unmanaged resource. CLR will ensure SafeHandles get freed, without requiring a finalizer on this class.
		/// </summary>
		private readonly SafeLibraryHandle libraryHandle;

		/// <summary>
		/// Constructor to load a dll and be responible for freeing it.
		/// </summary>
		/// <param name="fileName">full path name of dll to load</param>
		/// <exception cref="System.IO.FileNotFoundException">if fileName can't be found</exception>
		/// <remarks>Throws exceptions on failure. Most common failure would be file-not-found, or
		/// that the file is not a  loadable image.</remarks>
		public UnmanagedLibrary(string fileName)
		{
			this.libraryHandle = NativeMethods.LoadLibrary(fileName);
			if (!this.libraryHandle.IsInvalid) {
				return;
			}
			var hr = Marshal.GetHRForLastWin32Error();
			Marshal.ThrowExceptionForHR(hr);
		}

		/// <summary>
		/// Dynamically lookup a function in the dll via kernel32!GetProcAddress.
		/// </summary>
		/// <param name="functionName">raw name of the function in the export table.</param>
		/// <returns>null if function is not found. Else a delegate to the unmanaged function.
		/// </returns>
		/// <remarks>GetProcAddress results are valid as long as the dll is not yet unloaded. This
		/// is very very dangerous to use since you need to ensure that the dll is not unloaded
		/// until after you're done with any objects implemented by the dll. For example, if you
		/// get a delegate that then gets an IUnknown implemented by this dll,
		/// you can not dispose this library until that IUnknown is collected. Else, you may free
		/// the library and then the CLR may call release on that IUnknown and it will crash.</remarks>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
		public TDelegate GetUnmanagedFunction<TDelegate>(string functionName) where TDelegate : class
		{
			var p = NativeMethods.GetProcAddress(this.libraryHandle, functionName);

			// Failure is a common case, especially for adaptive code.
			if (p == IntPtr.Zero) {
				return null;
			}
			var function = Marshal.GetDelegateForFunctionPointer(p, typeof(TDelegate));

			// Ideally, we'd just make the constraint on TDelegate be
			// System.Delegate, but compiler error CS0702 (constrained can't be System.Delegate)
			// prevents that. So we make the constraint system.object and do the cast from object-->TDelegate.
			object o = function;

			return (TDelegate)o;
		}

		protected override void OnDisposeUnmanaged()
		{
			base.OnDisposeUnmanaged();
			if (!this.libraryHandle.IsClosed) {
				this.libraryHandle.Close();
			}
		}
	}
}